﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Net;
using ExamSharedClasses;
using ExamSharedClasses.Commands;

namespace ExamServer {

    /// <summary>
    /// (k) Diese Klasse nimmt die Verbindungen an und handelt sie solange, bis der User ein erfolgreiches
    /// Login vorgenommen hat. Es wird dann hier ein ClientConnectionThread-Objekt erstellt, welches
    /// einen Thread startet, in dem permanent gelauscht wird.
    /// </summary>
    public class ClientConnection : Connection {

        public ClientConnection()
            : base() {
            // ConnectionThread() ("Vermittler-Thread") als Thread ausführen
            ConnectionThread = new Thread( new ThreadStart( ConnectionThreadMethod ) );
        }

        public override void ConnectionThreadMethod()   // SERVER-MAIN-THREAD
        {
            Display( "auf Verbindungen warten..." );
            Listen();

            while ( true ) {

                // hier wird die Verbindung angenommen, sobald vorhanden...
                Client = Listener.AcceptTcpClient();
                Stream = Client.GetStream();
                Formatter = new BinaryFormatter();

                try {

                    ProcessReceivedObject( Receive() );

                } catch ( Exception ex ) {
                    Display( "Fehler: Die Verbindung wurde unerwartet beendet!", ex );
                    
                }
            }
        }

        public void ProcessReceivedObject( object o ) {

            if ( o is Session ) {

                Session receivedSession = ( Session )o;
                Command receivedCommand = receivedSession.Command;
                User receivedUser = receivedSession.User;

                if ( receivedCommand is Message ) {
                    Display( "Nachricht: " + ( ( Message )receivedCommand ).MessageText );

                } else if ( receivedCommand is Ping ) {
                    Ping ping = ( Ping )receivedCommand;
                    if ( ping.Source != App.SOURCE ) 
                    { // Ping zurück an Client senden oder auswerten
                        Send( new Session( ping ));
                    } else
                        Display( "eigenen Ping verwerfen" );

                } else if ( receivedCommand is Login ) { // hier eventuelle Übergabe der Verbindung an ein (neues) ClientConnection-Objekt (Thread)
                    UserAddress receivedUserAddress = ( UserAddress )receivedUser;
                    if ( App.IsLoginAllowed && AuthenticateUser( ( Login )receivedCommand, ref receivedUserAddress, ref receivedSession ) ) {
                        Display( "Authentifikation des Users erfolgreich" );
                        // Die Verbindung in das ClientConnection-Objekt übergeben, damit die Verbindung fortgesetzt werden kann
                        ClientConnectionThread clientConnection = new ClientConnectionThread( Client, Stream, Formatter, receivedSession, receivedUserAddress );
                        App.ClientConnectionThreadList.Add( clientConnection );
                        clientConnection.Start();

                    } else
                        Display( "Fehler: Server läßt keine Anmeldungen zu!" );

                } else
                    Display( "Fehler: Objekt wird nicht unterstützt!" );

            } else
                Display( "Fehler: invalider Code!" );
        }

        public bool AuthenticateUser( Login login, ref UserAddress userAddress, ref Session session ) {
            bool success = false;
            Display( "Authentifizierung eines Users..." );
            DatabaseConnection databaseConnection = App.DatabaseConnection;
            try {
                // (k) nun anhand der email und des passworthashes ein User-Objekt ermitteln,
                // damit ein Passwort-objekt ermitteln und beide mit Login-Objekt abgleichen...
                userAddress = databaseConnection.GetUserAddress( login.Email );
                Password password = databaseConnection.GetPassword( userAddress.UserId );

                // Passwortüberprüfung, ergibt true, wenn_______________________________
                if ( password.PasswordHash.Equals( login.Password.PasswordHash ) &&  // der Login-Passwordhash mit demselben des ermittelten übereinstimmt, und
                    userAddress.UserGroup.GroupId >= 0 )                            // die GroupId größer ist, als 0, also ein gültiges User-Objekt ermittelt wurde
                {
                    session.User = userAddress;
                    session.GenerateSessionId();
                    session.Command = new Message( 667, "Login erlaubt...", App.SOURCE );
                    Send( session );
                    success = true;

                } else {
                    session = new Session();
                    session.Command = new Message( 666, "Anmeldung fehlgeschlagen", App.SOURCE );
                    Send( session );
                }


            } catch ( Exception ex ) {
                Display( "Fehler: Fehler bei der Anmeldung!", ex );
                success = false;
            }
            return success;
        }

        public override object Receive() {
            try {
                object o = base.Receive();
                Display( "EMPFANGEN: " + o.ToString() );
                return o;
            } catch ( Exception ex ) {
                Display( ex );
                Close();
                Listen();
                return null;
            }
        }
        public override void Send( object o ) {
            try {                                   // automatische Vorgänge:
                Session s = ( Session )o;

                Command c = s.Command;
                if ( c.Source.Equals( "" ) )        //  -Commando.Source-Property ausfüllen, wenn nicht schon geschehen...
                    c.Source = App.SOURCE;

            } catch ( Exception ex ) {
                Display( ex );

            }

            try {
                Display( "SENDE: " + o.ToString() );
                base.Send( o );
            } catch ( Exception ex ) {
                Display( ex );
                Close();
                Listen();
            }
        }
        /* // soll im moment der thread machen (Handle())
        public void SendAndReceive( object o ) {
            Send( o );
            //Receive(); 
        }
        public object SendAndReceiveAndReturnObject( object o ) {
            Send( o );
            return ReceiveAndReturnObject();
        }*/

        public void Display( object messageObject ) {
            Display( messageObject, null );
        }
        public void Display( object messageObject, Exception ex ) {
            string messageString = "VermittlerThread: " + messageObject.ToString();
            App.Display( messageString, ex );
        }




    }
}